#include "../SimpleWnd/SimpleWnd.h"
#include <assert.h>
#include <windows.h>
#include <windowsx.h>
#include <shlwapi.h>
#include "EditWindow.h"
#include "EditGoDialog.h"
#include "FindReplaceDlg.h"

#define TRAILING_CHARS 50
#define LEADING_SPACE 5
#define FONT_NAME "Courier New"
#define FONT_SIZE 16
#define MAX_LINENBR_DIGITS 16

#define MAX(a,b) (a>b ? a : b)
#define MIN(a,b) (a<b ? a : b)

static HFONT CreateEditFont();
static UINT GetLineNumberAreaWidth (UINT uWndHeight, UINT uLineHeight, UINT uStartLine, UINT uCharWidth);
static char* CreateSpaceStringForTab(size_t spaces);

#define HIDE_CARET()     do { \
            if (!m_bCaretHidden) { \
                HideCaret(); \
                m_bCaretHidden = true; \
            } \
        } while (0)

#define SHOW_CARET()    do { \
            if (GetFocus() == m_hWnd) { \
                ShowCaret(); \
                m_bCaretHidden = false; \
            } \
        } while (0)


#define SEL_COL_2_IDX(sl,sc,bl,bc,si,bi) \
            do {\
                CTextLine* line = m_doc.GetLineObject(sl);\
                si = line->DisplayColToIndex(sc, m_uTabSize);\
                line = m_doc.GetLineObject(bl);\
                if (bc == EOL) {\
                    bi = EOL;\
                } else {\
                    UINT bi_left, bi_right; \
                    bi_right = line->DisplayColToIndex(bc, m_uTabSize, true);\
                    bi_left = line->DisplayColToIndex(bc, m_uTabSize); \
                    (bi_left != bi_right ? bi = bi_right -1 : bi = bi_right); \
                }\
            } while (0)


CEditWindow::CEditWindow()
{
    m_hFont = CreateEditFont();
    m_bShowLineNumber = true;
    m_bSpaceForTab = true;
    m_uFirstShownLine = 0;
    m_uFirstShownChar = 0;
    m_brLineNbr = (HBRUSH)(COLOR_SCROLLBAR+1); 
    m_brText = (HBRUSH)(COLOR_WINDOW+1);
    m_brTextCur = CreateSolidBrush(RGB(0xd0, 0xd0, 0xff));
    m_uCharWidth = 0;
    m_uLineHeight = 0;
    m_uCurLine = 0;
    m_uCurCol = 0;
    m_uTargetedCol = 0;
    m_hCursor = NULL;
    m_hCursorMove = NULL;
    m_hCursorCopy = NULL;

    m_uTabSize = 3;
    m_bCaretHidden = false;
    m_doc.SetCrLf("\r\n");
    m_hIMC = NULL;
    m_hEditMenu = LoadMenu(_Module.GetModuleInstance(), MAKEINTRESOURCE(IDR_EDITCTL));
    m_dlgFindReplace = new CFindReplaceDlg(this);
    m_selFind.SetSelection(0,0, 0,0);
    m_sel.SetSelection(0,0,0,0);
    m_selRestore.SetSelection(0,0,0,0);
    m_selFound.SetSelection(0,0,0,0);
    m_strFind = NULL;

    char szSyntaxPath[_MAX_PATH];
    GetModuleFileName(_Module.GetModuleInstance(), szSyntaxPath, _MAX_PATH);
    PathRemoveFileSpec(szSyntaxPath);
    strcat(szSyntaxPath, "\\");
    strcat(szSyntaxPath, DEFAULT_SYNTAXFILE);
    m_arrSyntax.ReadFromFile(szSyntaxPath);

    m_szFilename[0] = '\0';
    m_syntaxLighter.SetSyntax(NULL);
    m_dragMode = NoDrag;
    m_uDebugLineNo = LAST_LINE; /* No debug line.*/
    m_brDebugLine = CreateSolidBrush(DEBUG_LINE_COLOR);
    m_brBreakPoint = CreateSolidBrush(BREAKPOINT_COLOR);
    m_pBkPtMgr = NULL;
    m_pDebugger = NULL;
    m_ptTip.x = -1;
    m_ptTip.y = -1;
}

CEditWindow::~CEditWindow()
{
    if (m_hFont) {
        DeleteObject(m_hFont);
    }
    if (m_brTextCur) {
        DeleteObject (m_brTextCur);
    }
    if (m_brDebugLine) {
        DeleteObject (m_brDebugLine);
    }
    if (m_brBreakPoint) {
        DeleteObject (m_brBreakPoint);
    }
    if (m_hEditMenu) {
        DestroyMenu (m_hEditMenu);
    }
    if (m_dlgFindReplace) {
        delete m_dlgFindReplace;
    }
    if (m_strFind) {
        free (m_strFind);
    }
}

void CEditWindow::ReadFromFile(LPCSTR fn)
{
    FILE* f = fopen(fn , "rb");
    if (!f) {
        ::MessageBox(m_hWnd, "Cannot open this file.", "File open Error", MB_ICONSTOP|MB_OK);
        return;
    }
    fclose (f);
    
    SetScrollRange(SB_HORZ, 0, 0, FALSE);
    ShowScrollBar(SB_HORZ, FALSE);
    SetScrollRange(SB_VERT, 0, 0, FALSE);
    ShowScrollBar(SB_VERT, FALSE);

    m_doc.SetCrLf("\r\n");
    m_doc.ReadFile(fn);
    
    strncpy (m_szFilename, fn, _MAX_PATH);
    CSyntaxConfig* syn;
    m_arrSyntax.FindSyntaxByFilename(fn, &syn);
    if (syn) {
        m_syntaxLighter.SetSyntax(syn);
        m_syntaxLighter.SetDocument(&m_doc);
        m_syntaxLighter.FindAllBlocks();
    }

    m_uFirstShownLine = 0;
    m_uFirstShownChar = 0;
    m_uCurLine = m_uCurCol = 0;
    m_selFind.SetSelection(0,0, 0,0);
    m_sel.SetSelection(0,0,0,0);
    
    m_stack.ClearAll();

    SetFocus();
    SetScrollBars();
    
    Invalidate(FALSE);
}

bool CEditWindow::IsDirty()
{
    return m_doc.IsDirty();
}

void CEditWindow::SetDirty(bool dirty/*=true*/)
{
    m_doc.SetDirty(dirty);
}

void CEditWindow::CloseFile()
{
    m_doc.CloseDoc();

    m_syntaxLighter.SetSyntax(NULL);

    SetScrollRange(SB_HORZ, 0, 0, FALSE);
    ShowScrollBar(SB_HORZ, FALSE);
    SetScrollRange(SB_VERT, 0, 0, FALSE);
    ShowScrollBar(SB_VERT, FALSE);
    m_uFirstShownLine = 0;
    m_uFirstShownChar = 0;
    m_uCurLine = m_uCurCol = 0;
    m_selFind.SetSelection(0,0, 0,0);
    m_sel.SetSelection(0,0,0,0);

    m_stack.ClearAll();
    SetFocus();
    Invalidate(FALSE);
}

void CEditWindow::SaveToFile(LPCSTR fn)
{
    m_doc.WriteFile(fn);
    strncpy (m_szFilename, fn, _MAX_PATH);
    
    CSyntaxConfig* syn;
    m_arrSyntax.FindSyntaxByFilename(fn, &syn);
    if (syn) {
        m_syntaxLighter.SetSyntax(syn);
        m_syntaxLighter.SetDocument(&m_doc);
        m_syntaxLighter.FindAllBlocks();
    }
    Invalidate(FALSE);
}

void CEditWindow::SetTabSize(UINT size)
{
    m_uTabSize = (size & 0xf);
    if (m_uTabSize == 0) {
        m_uTabSize = 3;
    }
    if (IsWindow()) {
        Invalidate(FALSE);
    }
}

void CEditWindow::SetSpaceForTab(bool b)
{
    m_bSpaceForTab = b;
}

void CEditWindow::SetSyntaxFile (const char* fn)
{
    m_arrSyntax.ReadFromFile(fn);
    CSyntaxConfig* syn;
    m_arrSyntax.FindSyntaxByFilename(m_szFilename, &syn);
    if (syn) {
        m_syntaxLighter.SetSyntax(syn);
        m_syntaxLighter.SetDocument(&m_doc);
        m_syntaxLighter.FindAllBlocks();
    }
    if (IsWindow()) {
        Invalidate(FALSE);
    }
}

unsigned int CEditWindow::GetCurLine()
{
    return m_uCurLine;
}

void CEditWindow::SetBkPtMgr(IBkPtMgr* mgr)
{
   m_pBkPtMgr = mgr;
   m_doc.SetBkPtMgr(mgr);
}

void CEditWindow::SetDebuggerIF(IDebuggerIF2Editor* pDebugger)
{
   m_pDebugger = pDebugger;
}

void CEditWindow::SetDebugLineNo(unsigned int lineno)
{
    m_uDebugLineNo = lineno;
    
    if (lineno < m_doc.m_nValidLines) {
        m_uCurLine = lineno;
        m_uCurCol = 0;
        SetCurPosVisible();
    }
    Invalidate(FALSE);
}

#define STACK_SIZE 1024

LRESULT CEditWindow::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    m_hCursor = LoadCursor(NULL, IDC_IBEAM);
    SetClassLong(m_hWnd, GCL_HCURSOR, (LONG)m_hCursor);

    m_hCursorMove = LoadCursor(_Module.GetModuleInstance(), MAKEINTRESOURCE(IDC_DRAGMOVE)); 
    m_hCursorCopy = LoadCursor(_Module.GetModuleInstance(), MAKEINTRESOURCE(IDC_DRAGCOPY));

    m_hIMC = ImmCreateContext();
    if (m_hIMC) {
        ImmAssociateContext(m_hWnd, m_hIMC);
    }
    m_stack.SetStackSize(STACK_SIZE);
    m_stack.SetDocument(&m_doc);
    m_doc.SetStack(&m_stack);
    
    RECT rcFoo = {0, 0, 1, 1};
    m_ctrlTip.Create(m_hWnd, rcFoo, NULL, WS_POPUP|WS_BORDER);
    return 0;
}

LRESULT CEditWindow::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    if (m_hCursorMove) {
        DestroyCursor(m_hCursorMove);
    }
    if (m_hCursorCopy) {
        DestroyCursor(m_hCursorCopy);
    }
    if (m_hIMC) {
        ImmDestroyContext(m_hIMC);
        m_hIMC = NULL;
    }
    if (m_ctrlTip.IsWindow()) {
        m_ctrlTip.DestroyWindow();
    }
    return 0;
}
 
HWND CEditWindow::CreatePaneWindow(HWND hParent, RECT& rc, DWORD dwStyle, DWORD dwExStyle)
{
    assert (hParent);
    m_hParent = hParent;
    
    dwExStyle |=  WS_EX_CLIENTEDGE;
    dwStyle |= WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
    m_hWnd = Create (hParent, rc, NULL, dwStyle, dwExStyle);

    if (!m_hWnd) {
        DWORD err = GetLastError();
        return NULL;
    }
    ::ShowWindow(m_hWnd, SW_SHOW);
    ::UpdateWindow(m_hWnd);
    return m_hWnd;
}

LRESULT CEditWindow::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    PAINTSTRUCT ps;
    TEXTMETRIC tm;
    UINT uStartLine, uEndLine, uLineNbrWidth;
    RECT rcClient;
    bool bFirstDraw = false;

    if (m_uLineHeight == 0 || m_uCharWidth == 0) {
        bFirstDraw = true;
    }

    HIDE_CARET();

    BeginPaint(&ps);
    RECT rc = ps.rcPaint;
    HDC hDC = ps.hdc;
    if (m_hFont) {
        SelectObject (hDC, m_hFont);
    }
    GetTextMetrics(hDC, &tm);
    m_uLineHeight = tm.tmHeight + tm.tmExternalLeading;
    m_uCharWidth = tm.tmAveCharWidth;

    uStartLine = rc.top / m_uLineHeight + m_uFirstShownLine;
    uEndLine = rc.bottom/m_uLineHeight;
    uEndLine = (rc.bottom%m_uLineHeight==0 ? uEndLine : uEndLine+1);
    uEndLine += m_uFirstShownLine;
    GetClientRect(&rcClient);
    uLineNbrWidth = GetLineNumberAreaWidth(rcClient.bottom-rcClient.top, 
                                            m_uLineHeight,
                                            m_uFirstShownLine,
                                            tm.tmAveCharWidth);
    UINT i;
    RECT rcLine;
    rcLine.left = rcClient.left;
    rcLine.right = rcClient.right;
    rcLine.top = (uStartLine - m_uFirstShownLine) * m_uLineHeight;
    rcLine.bottom = rcLine.top + m_uLineHeight;

    for (i = uStartLine; i <= uEndLine; i++) {
        DrawLineNumber(hDC, i, uLineNbrWidth, &rcLine);
        DrawLineText(hDC, i, uLineNbrWidth, &rcLine);
        rcLine.top += m_uLineHeight;
        rcLine.bottom += m_uLineHeight;
    }
    EndPaint(&ps);
    SetCaretPosAndShow();
    if (bFirstDraw) {
        SetFocus();
    }
    return 0;
}

LRESULT CEditWindow::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    if (m_uLineHeight * m_uCharWidth != 0) {
        SetScrollBars();
    }
    return 0;
}

LRESULT CEditWindow::OnScroll(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    bHandled = TRUE;
    
    WORD wCode = LOWORD(wParam);
    if (wCode == SB_ENDSCROLL)
        return 0;

    int nBar = (uMsg == WM_HSCROLL ? SB_HORZ : SB_VERT);
    long lPos = GetScrollPos(nBar);
    int min,max;
    GetScrollRange(nBar, &min, &max);
    lPos = CalcSBPos (nBar, wParam, lPos, max);
    if (nBar == SB_HORZ) {
        m_uFirstShownChar = lPos;    
    } else if (nBar == SB_VERT) {
        m_uFirstShownLine = lPos;
    } 
    SetScrollPos(nBar, lPos);
    Invalidate (FALSE);
    return 0;
}

#define CARET_WIDTH 2

LRESULT CEditWindow::OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    SetCompositionFont();
    CreateSolidCaret(CARET_WIDTH, m_uLineHeight);
    m_bCaretHidden = false;
    SetCaretPosAndShow();
    return 0;
}

LRESULT CEditWindow::OnKillFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    DestroyCaret();
    m_bCaretHidden = true;
    return 0;
}

LRESULT CEditWindow::OnLBtnDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    unsigned int line, col;
    
    XY2LineCol(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), &line, &col);

    if (!m_sel.InSelection(line, col)) {
        m_dragMode = NoDrag;
        SetCapture();
        ChangeCurLineByMouse(lParam);
        SetCurPosVisible();
        m_sel.SetSelection(m_uCurLine, m_uCurCol, m_uCurLine, m_uCurCol);
        return 0;
    }
    m_dragMode = DragMove;
    if (m_hCursorMove) {
        SetCapture();
        SetCursor(m_hCursorMove);
    }
    return 0;
}

LRESULT CEditWindow::OnLBtnUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    ReleaseCapture();
    if (m_dragMode == NoDrag) {
        return 0;
    }
    
    unsigned int line, col;
    XY2LineCol(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), &line, &col);

    if (!m_sel.InSelection(line, col)) {
        if (m_dragMode == DragCopy) {
            CopySelectionTo(line, col);
        } else if (m_dragMode == DragMove) {
            MoveSelectionTo(&line, &col);
            m_uCurLine = line;
            m_uCurCol = col;
        }
    }
    m_dragMode = NoDrag;
    m_sel.SetSelection(m_uCurLine, m_uCurCol, m_uCurLine, m_uCurCol);
    SetScrollBars();
    Invalidate(FALSE);
    return 0;
}

LRESULT CEditWindow::OnDblClk(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    // Before OnDblClk, OnLButtonDown is called first.
    CTextLine* line = m_doc.GetLineObject(m_uCurLine);
    unsigned int sel_start, sel_len;
    unsigned int idx = line->DisplayColToIndex(m_uCurCol, m_uTabSize);
    sel_len = line->GetTokenByIndex(idx, &sel_start);
    if (sel_len == 0) {
        return 0;
    }
    unsigned int sel_col1, sel_col2;
    sel_col1 = line->IndexToDisplayCol(sel_start, m_uTabSize);
    sel_col2 = sel_col1 + sel_len;
    m_sel.SetSelection(m_uCurLine, sel_col1, m_uCurLine, sel_col2);
    
    Invalidate(FALSE);
    return 0;
}

bool CEditWindow::InHoverRect(const LPPOINT pt)
{
   UINT width, height;
   SystemParametersInfo(SPI_GETMOUSEHOVERWIDTH, 0, &width, 0);
   SystemParametersInfo(SPI_GETMOUSEHOVERHEIGHT, 0, &height, 0);
   
   if ((pt->x >= m_ptTip.x - width/2) && (pt->x  <= m_ptTip.x + width/2) && 
      (pt->y >= m_ptTip.y - height/2) && (pt->y <= m_ptTip.y + height/2)) {
      return true;
   }
   return false;
}

LRESULT CEditWindow::OnMouseMove(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   POINT ptCur;
   ptCur.x = GET_X_LPARAM(lParam);
   ptCur.y = GET_Y_LPARAM(lParam);

   if (::IsWindow(m_ctrlTip.m_hWnd) && !InHoverRect(&ptCur)) {
      m_ctrlTip.ShowWindow(SW_HIDE);
   }

   if (!(wParam & MK_LBUTTON)) {
      TRACKMOUSEEVENT tme;
      tme.cbSize = sizeof (tme);
      tme.dwFlags = TME_HOVER;
      tme.hwndTrack = m_hWnd;
      tme.dwHoverTime = HOVER_DEFAULT;

      BOOL bRet = TrackMouseEvent(&tme);
      return 0;
   }

    if (GetFocus() != m_hWnd) {
        return 0;
    }

    if (m_dragMode == NoDrag) {
        AdjustViewPort (GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
        ChangeCurLineByMouse(lParam);
        m_sel.SetEnd(m_uCurLine, m_uCurCol);
        SetCurPosVisible();
        return 0;
    }

    if (m_dragMode == DragMove && m_hCursorMove) {
        SetCursor(m_hCursorMove);
    } else if (m_dragMode == DragCopy && m_hCursorCopy) {
        SetCursor(m_hCursorCopy);
    }

    AdjustViewPort (GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    ChangeCurLineByMouse(lParam);
    XY2LineCol(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), &m_uCurLine, &m_uCurCol);
    SetCurPosVisible();
    return 0;
}

LRESULT CEditWindow::OnRBtnDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    ChangeCurLineByMouse(lParam);
    SetCurPosVisible();
    return 0;
}

LRESULT CEditWindow::OnContextMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    POINT pt;
    pt.x = GET_X_LPARAM(lParam);
    pt.y = GET_Y_LPARAM(lParam);

    if (m_hEditMenu) {
        HMENU hMenu = GetSubMenu(m_hEditMenu, 0);
        UINT enable;

        enable = (m_sel.Selected() ? MF_ENABLED : MF_GRAYED);
        EnableMenuItem(hMenu, IDM_COPY, MF_BYCOMMAND|enable);
        EnableMenuItem(hMenu, IDM_CUT, MF_BYCOMMAND|enable);
        enable = (IsClipboardFormatAvailable(CF_TEXT) ? MF_ENABLED : MF_GRAYED);
        EnableMenuItem(hMenu, IDM_PASTE, MF_BYCOMMAND|enable);
        
        enable = (m_dlgFindReplace->IsWindow() ? MF_GRAYED : MF_ENABLED);
        EnableMenuItem(hMenu, IDM_FIND, MF_BYCOMMAND|enable);
        EnableMenuItem(hMenu, IDM_REPLACE, MF_BYCOMMAND|enable);

        TrackPopupMenuEx(hMenu, TPM_LEFTALIGN|TPM_TOPALIGN|TPM_LEFTBUTTON, pt.x, pt.y, m_hWnd, NULL);
    }
    return 0;
}

#define HORZ_MOVE_IN_ADVANCE 10

LRESULT CEditWindow::OnKeyDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    HIDE_CARET();
    switch (wParam) { 
    case VK_HOME:       // Home 
        OnVkHome();
        break; 

    case VK_END:        // End 
        OnVkEnd();
        break;
        
    case VK_PRIOR:      // Page Up 
        OnVkPrior();
        break; 

    case VK_NEXT:       // Page Down 
        OnVkNext();
        break; 

    case VK_LEFT:       // Left arrow 
        OnVkLeft();
        break; 

    case VK_RIGHT:      // Right arrow 
        OnVkRight();
        break; 

    case VK_UP:         // Up arrow 
        OnVkUp();
        break; 

    case VK_DOWN:       // Down arrow 
        OnVkDown();
        break; 

    case VK_DELETE:     // Delete
        m_doc.SetDirty();
        OnVkDel();
        break; 

    case VK_F3:         // find next
        OnVkF3();
        break;

    case VK_CONTROL:
        OnCtrlDown();
        break;
    } 

    SetCaretPosAndShow(); 

    return 0; 
}

LRESULT CEditWindow::OnKeyUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    switch (wParam) {
    case VK_CONTROL:
        OnCtrlUp();
        break;

    default:
        break;
    }
    return 0;
}

LRESULT CEditWindow::OnChar(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    BOOL dummy;
    switch (wParam) {
    case 0x01:
    /* ctrl-A*/
        SelectAll();
        break;
    case 0x03:
    /* ctrl-C*/
        OnCopy (0, 0, NULL, dummy);
        break;
    case 0x06:
        OnFind(0, 0, NULL, dummy);
        break;
    case 0x07:
    /* ctrl-G*/
        OnGoToLineNo();
        break;
    case 0x08:
    /* backspace */
        m_doc.SetDirty();
        OnBackSpace();
        break;
    case 0x09:
    /* tab key*/
        m_doc.SetDirty();
        OnTabKey();
        break;
    case 0x0D:
    /* return key.*/
        m_doc.SetDirty();
        OnReturnKey();
        break;

    case 0x12:
    /* Ctrl-R */
        OnReplace(0,0, NULL, dummy);
        break;

    case 0x16:
    /* Ctrl-V*/
        m_doc.SetDirty();
        OnPaste (0, 0, NULL, dummy);
        break;
    case 0x18:
    /* Ctrl-X */
        m_doc.SetDirty();
        OnCut (0, 0, NULL, dummy);
        break;
    case 0x19:
    /* Ctrl-Y */
        ReDo();
        break;    
    case 0x1A:
    /* Ctrl-Z */
        UnDo();
        break;

    case 0x02:
    case 0x04:
    case 0x05:
    case 0x0A:
    case 0x0B:
    case 0x0C:
    case 0x0E:
    case 0x0F:
    case 0x10:
    case 0x11:
    case 0x13:
    case 0x14:
    case 0x15:
    case 0x17:
        break;

    default:
        m_doc.SetDirty();
        InsertCharToCurPos ((char)wParam);
        CheckForUnindent();
        break;
    }
    SetCurPosVisible();

    /* if text changed the previous find selection is invalidated.*/
    m_selFind.SetSelection(0,0,LAST_LINE,EOL);
    m_selRestore.SetSelection(0,0,0,0);

    Invalidate(FALSE);
    return 0;
}

void CEditWindow::CheckForUnindent()
{
    UINT uIdx;
    CTextLine* line;
    char* token = NULL;
    UINT token_start, token_len;
    UINT token_col, target_col, target_idx;

    line = m_doc.GetLineObject(m_uCurLine);
    token = line->GetFirstTokenAndPos (&token_start);
    if (!token) {
        return;
    }

    token_len = strlen (token);
    uIdx = line->DisplayColToIndex(m_uCurCol, m_uTabSize);
    if (!(uIdx >token_start && uIdx <= token_start+token_len)) {
        // If current insertion is not in the 1st token, no unindenting.
        goto Free_And_Return;
    }

    if (!m_syntaxLighter.IsUnindent(token)) {
        goto Free_And_Return;
    }

    token_col = line->IndexToDisplayCol(token_start, m_uTabSize);
    if (token_col % m_uTabSize == 0) {
        target_col = (token_col >= m_uTabSize ? token_col - m_uTabSize : 0);
    } else {
        target_col = (token_col/m_uTabSize) * m_uTabSize;
    }
    target_idx = line->DisplayColToIndex(target_col, m_uTabSize);
    
    if (token_start > target_idx) {
        m_doc.RemoveText(m_uCurLine, target_idx, m_uCurLine, token_start-1);
        m_uCurCol = m_uCurCol - (token_col-target_col);
    }
Free_And_Return:
    if (token) {
        free (token);
    }
    return;
}

void CEditWindow::OnReturnKey()
{
    UINT idx;
    char str[3];

    strncpy (str, "\r\n", sizeof(str));

    DeleteSelected();

    CTextLine* line = m_doc.GetLineObject(m_uCurLine);
    idx = line->DisplayColToIndex(m_uCurCol, m_uTabSize);

    m_stack.StartTransaction();
    m_doc.InsertTextAt(m_uCurLine, idx, str);

    // copy the previous line leading spaces.
    char* leadingspace = line->GetLeadingSpaces();
    if (leadingspace) {
        idx = strlen(leadingspace);
    } else {
        idx = 0;
    }

    m_uTargetedCol = line->IndexToDisplayCol(idx, m_uTabSize);
    if (leadingspace) {
        m_doc.InsertTextAt(m_uCurLine+1, 0, leadingspace);
    }
    char* last_token = line->GetLastToken();
    if (last_token && m_syntaxLighter.IsIndent(last_token)) {
        if (!m_bSpaceForTab) {
            str[0] = '\t';
            str[1] = '\0';
            m_doc.InsertTextAt(m_uCurLine+1, idx, str); 
        } else {
            size_t spaces = m_uTabSize - m_uTargetedCol%m_uTabSize;
            char* tabStr = CreateSpaceStringForTab (spaces);
            if (!tabStr) {
                return;
            }
            m_doc.InsertTextAt(m_uCurLine+1, idx, tabStr);
            free (tabStr);
        }

        m_uTargetedCol = (m_uTargetedCol / m_uTabSize + 1) * m_uTabSize;
    }
    m_stack.EndTransaction();
    if (last_token) {
       free (last_token);
    }
    if (leadingspace) {
       free(leadingspace);
    }
    SetScrollBars();
    m_syntaxLighter.FindAllBlocks();
    OnVkDown();
}

void CEditWindow::OnCtrlDown()
{
    if (m_dragMode == NoDrag) {
        return;
    }

    m_dragMode = DragCopy;
    SetCursor(m_hCursorCopy);
}

void CEditWindow::OnCtrlUp()
{
    if (m_dragMode == NoDrag) {
        return;
    }

    m_dragMode = DragMove;
    SetCursor(m_hCursorMove);
}

void CEditWindow::OnTabKey()
{
    UINT sl, sc, bl, bc;
    bool bShiftPressed = (GetKeyState(VK_SHIFT) < 0 ? true : false);
    char* tabStr;
    
    m_sel.GetAscendantInclusiveSel(&sl, &sc, &bl, &bc);

    if (!m_sel.Selected() || (sl == bl && bc != EOL)) {
        if (!m_bSpaceForTab) {
            InsertCharToCurPos ('\t');
            return;
        }
        size_t spaces = m_uTabSize - m_uCurCol%m_uTabSize;
        tabStr = CreateSpaceStringForTab (spaces);
        if (!tabStr) {
            return;
        }
        InsertTextToCurPos(tabStr);
        free (tabStr);
        return;
    }
    
    UINT line;
    if (!bShiftPressed) {
        if (!m_bSpaceForTab) {
            tabStr = "\t";
        } else {
            tabStr = CreateSpaceStringForTab (m_uTabSize);
            if (!tabStr) {
                return;
            }
        }
        m_stack.StartTransaction();
        for (line = sl; line <= bl; line++) {
            m_doc.InsertTextAt(line, 0, tabStr);
        }
        m_stack.EndTransaction();
        m_syntaxLighter.FindAllBlocks();
        m_sel.SetSelection(sl, 0, bl, EOL);
        if (m_bSpaceForTab) {
            free (tabStr);
        }
        return;
    }

    /* shift pressed. */
    m_stack.StartTransaction();
    for (line = sl; line <= bl; line++) {
        UINT idx;
        CTextLine* lineobj = m_doc.GetLineObject(line);
        UINT totalcol = lineobj->GetTotalCols(m_uTabSize);
        UINT col = (totalcol > m_uTabSize ? m_uTabSize : totalcol);
        idx = lineobj->DisplayColToIndex(col, m_uTabSize);
        m_doc.RemoveLeadingSpacesBefore(line, idx);
    }
    m_stack.EndTransaction();
    m_syntaxLighter.FindAllBlocks();

    return;
}

void CEditWindow::OnGoToLineNo()
{
    CEditGoDlg dlg;
    if (dlg.DoModal(m_hWnd) == IDCANCEL) {
        SetFocus();
        return;
    }
    m_uCurLine = (dlg.m_uLineNo < m_doc.m_nValidLines ?
                            dlg.m_uLineNo-1 : m_doc.m_nValidLines -1);
    m_uCurCol = 0;
    SetCurPosVisible();
    Invalidate(FALSE);
    SetFocus();
}

LRESULT CEditWindow::OnImeComposition(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    assert (IsWindow());

    bHandled = true;
    if (!(lParam & GCS_RESULTSTR)) {
        return (::DefWindowProc(m_hWnd, WM_IME_COMPOSITION, wParam, lParam));
    }
    
    UINT uIdx;
    CTextLine* line;

    m_stack.StartTransaction();
    DeleteSelected();

    line = m_doc.GetLineObject(m_uCurLine);
    uIdx = line->DisplayColToIndex(m_uCurCol, m_uTabSize);
    if (InsertImeString() != 0) {
        m_stack.EndTransaction();
        return (::DefWindowProc(m_hWnd, WM_IME_COMPOSITION, wParam, lParam));
    }
    m_stack.EndTransaction();

    SetScrollBars();
    SetCurPosVisible();
    /* if text changed the previous find selection is invalidated.*/
    m_selFind.SetSelection(0,0,LAST_LINE,EOL);
    m_selRestore.SetSelection(0,0,0,0);

    Invalidate(FALSE);

    return 0;
}

#define SCROLL_LINES 3

LRESULT CEditWindow::OnMouseWheel(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
    short zDelta = ((short)HIWORD(wParam) > 0 ? 1 : -1);
    
    assert (m_uLineHeight);
    assert (IsWindow());

    int min, max;
    GetScrollRange(SB_VERT, &min, &max);
    assert (min == 0);
    if (max == 0) {
        return 0;
    }

    if (zDelta > 0) {
        m_uFirstShownLine = (m_uFirstShownLine > SCROLL_LINES ? m_uFirstShownLine -SCROLL_LINES : 0);
    } else if (zDelta < 0) {
        if ((m_uFirstShownLine + GetShownLines() + SCROLL_LINES) < m_doc.m_nValidLines) {
            m_uFirstShownLine += SCROLL_LINES;
        } else {
            m_uFirstShownLine = m_doc.m_nValidLines - GetShownLines();
        }
    }
    
    m_uFirstShownLine = ((int)m_uFirstShownLine < max ? m_uFirstShownLine : max);
    SetScrollPos(SB_VERT, m_uFirstShownLine, TRUE);
    Invalidate(FALSE);

    return 0;
}

LRESULT CEditWindow::OnMouseHover(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   if (wParam & (MK_LBUTTON  | MK_RBUTTON)) {
      return 0;
   }
   if (!m_pDebugger) {
      return 0;
   }
   int nStartLine, nEndLine;
   m_pDebugger->GetCurRtnLineNoRange(&nStartLine, &nEndLine);
   if (nStartLine == 0 || nEndLine == 0) {
      /* No running, or not in debug mode. */
      return 0;
   }
   unsigned int nLine, nCol;
   POINT pt;
   pt.x = GET_X_LPARAM(lParam);
   pt.y = GET_Y_LPARAM(lParam);
   XY2LineCol(pt.x, pt.y, &nLine, &nCol);
   if (nLine >= m_doc.m_nValidLines || (int)nLine < nStartLine - 1 || (int)nLine > nEndLine) {
      /* Out of the document or out of current selected routine.*/
      return 0;
   }
   CTextLine* line = m_doc.GetLineObject(nLine);
   if (nCol >= line->GetTotalCols(m_uTabSize)) {
      return 0;
   }
   unsigned int sel_start, sel_len;
   unsigned int idx = line->DisplayColToIndex(nCol, m_uTabSize);
   sel_len = line->GetTokenByIndex(idx, &sel_start);
   if (sel_len == 0) {
      return 0;
   }
   
   TCHAR* token = new TCHAR[sel_len+1];
   if (!token) {
      return 0;
   }
   token[sel_len] = _T('\0');
   for (unsigned int i = 0; i < sel_len; i++) {
      token[i] = (*line)[sel_start + i];
   }
   _TRACE(_T("%s = "), token);
   TCHAR value [1024];
   if (m_pDebugger && m_pDebugger->GetVaribleValueInCurRtn(token, value, sizeof(value)) == 0) {
      m_ptTip = pt;
      ClientToScreen(&pt);
      pt.y -= m_uLineHeight;
      m_ctrlTip.SetText(&pt, value);
   }
   _TRACE(_T("\n"));
   delete [] token;
   return 0;
}

LRESULT CEditWindow::OnMouseLeave(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   if (::IsWindow(m_ctrlTip.m_hWnd)) {
      m_ctrlTip.ShowWindow(SW_HIDE);
   }
   return 0;
}

void CEditWindow::FindReplaceDlgDestroyed()
{
    assert (m_dlgFindReplace);
    if (m_dlgFindReplace->IsReplaceDlg()) {
        m_sel = m_selRestore;
    }
    SetFocus();
    Invalidate(FALSE);
    return;
}

void CEditWindow::FindNext(const char* str, bool up/*=false*/)
{
    assert (str);
    assert (str[0] != '\0');
    
    if (!SetFindString(str)) {
        return;
    }
    SetFocus();
    FindNextInDoc(m_strFind, &m_selFound, up);
    SetCurPosVisible();
    Invalidate(FALSE);
}

void CEditWindow::Replace(const char* str0, char* str1, bool up/*=false*/)
{
    assert (str0);
    assert (str0[0] != '\0');
    assert (str1);

    if (!SetFindString(str0)) {
        return;
    }
    
    if (CompareSelection(&m_selFound, m_strFind) == 0) {
        UINT sl,si,bl,bi, index_for_next;
        m_selFound.GetAscendantInclusiveSel(&sl, &si, &bl, &bi);

        assert (sl == bl);
        m_stack.StartTransaction();
        m_doc.RemoveText(sl, si, bl, bi);
        m_doc.InsertTextAt(sl, si, str1);
        m_stack.EndTransaction();

        m_doc.SetDirty();
        m_syntaxLighter.FindAllBlocks();
        AdjustFindSelEndingCol(&m_selFound, str0, str1);
        index_for_next = ( up ? si : si + strlen(str1));
        m_uCurCol = m_doc.GetLineObject(sl)->IndexToDisplayCol(index_for_next, m_uTabSize);
    }
    FindNextInDoc(m_strFind, &m_selFound, up);

    SetFocus();
    SetCurPosVisible();
    Invalidate(FALSE);
    return;
}

void CEditWindow::ReplaceAll(const char* str0, char* str1)
{
    assert (str0);
    assert (str0[0] != '\0');
    assert (str1);
    assert (m_selFind.Selected());

    if (!SetFindString(str0)) {
        return;
    }

    UINT sl, sc, bl, bc;
    m_selFind.GetAscendantSel(&sl, &sc, &bl, &bc);
    m_uCurLine = (sl == LAST_LINE ? m_doc.m_nValidLines-1 : sl);
    m_uCurCol = (sc == EOL ? 
                    m_doc.GetLineObject(m_uCurLine)->GetTotalCols(m_uTabSize) :
                    sc);
    m_stack.StartTransaction();
    do {
        FindNextInDoc(str0, &m_selFound);
        if (!m_selFound.Selected()) {
            break;
        }
        UINT si, bi, index_for_next;
        m_selFound.GetAscendantInclusiveSel(&sl, &si, &bl, &bi);
        assert (sl == bl);
        m_doc.RemoveText(sl, si, bl, bi);
        m_doc.InsertTextAt(sl, si, str1);
        m_doc.SetDirty();
        m_syntaxLighter.FindAllBlocks();
        AdjustFindSelEndingCol(&m_selFound, str0, str1);
        index_for_next = si + strlen(str1);
        m_uCurCol = m_doc.GetLineObject(sl)->IndexToDisplayCol(index_for_next, m_uTabSize);
    } while (m_selFound.Selected());
    m_stack.EndTransaction();

    SetFocus();
    SetCurPosVisible();
    Invalidate(FALSE);
}

LRESULT CEditWindow::OnCopy(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    UINT sl, sc, bl, bc;
    UINT si, bi;
    if (!m_sel.GetAscendantInclusiveSel(&sl,&sc,&bl,&bc)) {
        return 0;
    }
    SEL_COL_2_IDX(sl,sc,bl,bc,si,bi);
    size_t size = m_doc.GetTextLength(sl,si,bl,bi) + 1;

    if (!OpenClipboard()) {
        return 0; 
    }
    EmptyClipboard(); 

    HGLOBAL hglbCopy = GlobalAlloc(GMEM_MOVEABLE, size); 
    if (hglbCopy == NULL) { 
        CloseClipboard(); 
        return 0; 
    } 

    // Lock the handle and copy the text to the buffer. 
    LPSTR lpstr = (LPSTR)GlobalLock(hglbCopy);
    memset (lpstr, 0, size);
    m_doc.GetText(sl,si,bl,bi, lpstr, size);
    GlobalUnlock(hglbCopy); 
    SetClipboardData(CF_TEXT, hglbCopy); 
    CloseClipboard(); 
    return 0;
}

LRESULT CEditWindow::OnPaste(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    HGLOBAL hglb;

    if (!IsClipboardFormatAvailable(CF_TEXT)) 
        return 0; 
    if (!OpenClipboard()) 
        return 0; 

    hglb = GetClipboardData(CF_TEXT); 
    if (hglb == NULL) {
        return 0;
    }

    LPSTR lpstr = (LPSTR)GlobalLock(hglb); 
    if (lpstr != NULL) {
        InsertTextToCurPos(lpstr);
        GlobalUnlock(hglb); 
    }

    CloseClipboard(); 
    return 0;
}

LRESULT CEditWindow::OnCut(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    OnCopy(wNotifyCode, wID, hWndCtl, bHandled);
    DeleteSelected();
    m_doc.SetDirty();
    SetScrollBars();
    SetCurPosVisible();
    Invalidate(FALSE);
    return 0;
}

LRESULT CEditWindow::OnFind(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    assert (m_dlgFindReplace);
    assert (!m_dlgFindReplace->IsWindow());
    
    m_selFind.SetSelection(0, 0, LAST_LINE, EOL);
    m_dlgFindReplace->SetAsReplaceDlg(false);
    m_dlgFindReplace->Create(m_hWnd);

    return 0;
}

LRESULT CEditWindow::OnReplace(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
    assert (m_dlgFindReplace);
    assert (!m_dlgFindReplace->IsWindow());

    m_selRestore = m_sel;
    if (m_sel.Selected()) {
        m_selFind = m_sel;
    } else {
        m_selFind.SetSelection(0, 0, LAST_LINE, EOL);
    }
    m_selFound.SetSelection(0,0,0,0);
    m_dlgFindReplace->SetAsReplaceDlg(true);
    m_dlgFindReplace->Create(m_hWnd);

    return 0;
}

int CEditWindow::CalcSBPos (int nBar, WPARAM wParam, long curPos, long upperlimit)
{
    WORD wCode = LOWORD (wParam);
    UINT uScrollPage;

    if (nBar == SB_HORZ) {
        uScrollPage = GetShownChars();
    } else {
        uScrollPage = GetShownLines();
    }

    switch (wCode) {
    case SB_LINELEFT:
        (curPos > 0 ? --curPos : curPos = 0);
        break;
    case SB_PAGELEFT:
        (curPos - uScrollPage > 0 ? curPos = curPos - uScrollPage : curPos = 0);
        break;
    case SB_LINERIGHT:
        if (curPos < upperlimit) {
            curPos++;
        }
        break;
    case SB_PAGERIGHT:
        if (curPos + uScrollPage < (unsigned int)upperlimit) {
            curPos = curPos + uScrollPage;
        } else {
            curPos = upperlimit;
        }
        break;
    case SB_THUMBPOSITION:
    case SB_THUMBTRACK:
        curPos = HIWORD(wParam);
        break;
    }
    return curPos;
}

void CEditWindow::DrawLineNumber (HDC dc, UINT uLine, UINT uWidth, RECT* rcLine)
{
    char szLineNbr[MAX_LINENBR_DIGITS+1];

    if (!m_bShowLineNumber) {
        return;
    }
    RECT rc;
    rc.top = rcLine->top;
    rc.bottom = rcLine->bottom;
    rc.left = rcLine->left;
    rc.right = rc.left + uWidth;
    if (uLine == m_uDebugLineNo && m_pBkPtMgr && m_pBkPtMgr->IsBreakPoint(uLine+1)) {
        RECT rc_half = rc;
        rc_half.right = (rc.right + rc.left) /2;
        FillRect (dc, &rc_half, m_brBreakPoint);
        rc_half.left = rc_half.right;
        rc_half.right = rc.right;
        FillRect (dc, &rc_half, m_brDebugLine);
    } else if (uLine == m_uDebugLineNo) {
        FillRect (dc, &rc, m_brDebugLine);
    } else if (m_pBkPtMgr && m_pBkPtMgr->IsBreakPoint(uLine+1)) {
        FillRect (dc, &rc, m_brBreakPoint);
    } else {
        FillRect (dc, &rc, m_brLineNbr);
    }
    if (m_doc.m_nValidLines <= uLine) {
        return;
    }

    _snprintf (szLineNbr, MAX_LINENBR_DIGITS, "%d", uLine+1);
    SetTextColor (dc, GetSysColor(COLOR_WINDOWTEXT));
    SetBkMode(dc, TRANSPARENT);
    DrawText (dc, szLineNbr, -1, &rc, DT_BOTTOM | DT_SINGLELINE|DT_RIGHT);
}

void CEditWindow::DrawLineText (HDC dc, UINT uLine, UINT uLineNbrWidth, RECT* rcLine)
{
    /* Create memory DC and memory bitmap.
     *  bitmap size: height = line heigth
     *   width = (strlen(line text) + tariling_chars) * char_wdith + rcLine's width
     */
    HDC dcMem;
    HBITMAP bmp;
    UINT width = 0;
    RECT rcBmp;
    HBRUSH brText = (m_uCurLine == uLine ? m_brTextCur : m_brText);

    dcMem = CreateCompatibleDC(dc);
    if (m_doc.m_nValidLines > uLine) {
        width = (m_doc.GetLineObject(uLine))->GetTotalCols(m_uTabSize);
    }
    width += TRAILING_CHARS;
    width *= m_uCharWidth;
    width += rcLine->right - rcLine->left;
    bmp = CreateCompatibleBitmap(dc, width, m_uLineHeight);
    SelectObject(dcMem, bmp);
    rcBmp.left = 0;
    rcBmp.top = 0;
    rcBmp.right = width;
    rcBmp.bottom = m_uLineHeight;
    FillRect(dcMem, &rcBmp, brText);

    RECT rc, rcSpace;
    rc.top = rcLine->top;
    rc.bottom = rcLine->bottom;
    rc.left = rcLine->left + (m_bShowLineNumber ? uLineNbrWidth : 0);
    rc.right = rcLine->right;
    /* Draw leading space */
    rcSpace.left = rc.left;
    rcSpace.right = rc.left + LEADING_SPACE;
    rcSpace.top = rc.top;
    rcSpace.bottom = rc.bottom;
    FillRect(dc, &rcSpace, m_brText);
    rc.left += LEADING_SPACE;

    if (m_doc.m_nValidLines > uLine) {
        DrawColoredText(dcMem, uLine, &rcBmp);
    }
    
    BitBlt(dc, rc.left, rc.top,
            rc.right - rc.left,
            rc.bottom - rc.top,
            dcMem,
            m_uFirstShownChar*m_uCharWidth, 0,
            SRCCOPY);

    DeleteDC (dcMem);
    DeleteObject (bmp);
}


void CEditWindow::DrawColoredText(HDC dc, UINT uLine, RECT* rc)
{
    assert (uLine < m_doc.m_nValidLines);

    CTextLine* line = m_doc.GetLineObject(uLine);
    UINT len = strlen(line->GetArrayPtr());
    if (len == 0) {
        /* Empty string.
         * When selected, should show half char width hilight background.
         */
        if (!m_sel.InSelection(uLine, 0)) { 
            return;
        }
        RECT rcChar = *rc;
        rcChar.right = rcChar.left + m_uCharWidth / 2;
        HBRUSH brSel = (HBRUSH)(COLOR_HIGHLIGHT+1);
        FillRect (dc, &rcChar, brSel);
        return;
    }

    COLORREF* clr_ar; 

    int firstSelectedIdx, lastSelectedIdx;
    GetSelectedRangeInThisLine(uLine, len, &firstSelectedIdx, &lastSelectedIdx);
    char* pLineText = m_doc.GetLineText(uLine);
    if (IS_LEADING_CHAR(pLineText[lastSelectedIdx])) {
        lastSelectedIdx ++;
    }

    unsigned int ar_size = m_syntaxLighter.ColorTheLine(uLine, m_doc.GetLineText(uLine), &clr_ar);

    SelectObject(dc, m_hFont);
    SetBkMode(dc, TRANSPARENT); 

    UINT idxStart = 0;
    UINT idxEnd = 0; // exclusively
    RECT rcSeg = *rc; 
    while (idxEnd < len) {
        bool selected;
        FindSameColorSegment(clr_ar, len, idxStart, &idxEnd, firstSelectedIdx, lastSelectedIdx, selected);
        COLORREF clr;
        if (clr_ar) {
            clr = clr_ar[idxStart];
        } else {
            clr = GetSysColor(COLOR_WINDOWTEXT);
        }
        DrawColoredTextSegment(dc, line, idxStart, idxEnd - idxStart, clr, selected, &rcSeg);
        rcSeg.left = rcSeg.right;
        idxStart = idxEnd;
    }
    if (clr_ar) {
        free (clr_ar);
    }
}

void CEditWindow::GetSelectedRangeInThisLine(UINT uLine, UINT len, int* firstSelectedIdx, int* lastSelectedIdx)
{
    assert (uLine < m_doc.m_nValidLines);
    assert (firstSelectedIdx && lastSelectedIdx);

    *firstSelectedIdx = -1;
    *lastSelectedIdx = -1;
    CTextLine* line = m_doc.GetLineObject(uLine);

    if (m_sel.Selected()) {
        UINT sl, sc, bl, bc;
        m_sel.GetAscendantInclusiveSel(&sl, &sc, &bl, &bc);
        if (sl < uLine && bl >= uLine) {
            *firstSelectedIdx = 0;
        } else if (sl == uLine) {
            assert (sc != EOL);
            *firstSelectedIdx = line->DisplayColToIndex(sc, m_uTabSize);
        } else {
            return;
        }

        if (bl == uLine) {
            if (bc != EOL) {
                *lastSelectedIdx = line->DisplayColToIndex(bc, m_uTabSize);
            } else {
                *lastSelectedIdx = len -1;
            }
        } else if (bl > uLine && sl <= uLine) {
            *lastSelectedIdx = len - 1;
        }
    }
    return;
}

void CEditWindow::FindSameColorSegment(COLORREF* clr_ar, UINT len, UINT idxStart, UINT* idxEnd, int firstSel, int lastSel, bool& selected)
{
    *idxEnd = lastSel +1;
    if (idxStart >= (UINT)firstSel && idxStart <= (UINT)lastSel ) {
        *idxEnd = lastSel +1;
        *idxEnd = (*idxEnd > len ? len : *idxEnd);
        selected = true;
        return;
    }

    UINT uSearchEnd; 
    selected = false;
   *idxEnd = idxStart +1;
    if (firstSel == -1) {
        // No selection in this line
        uSearchEnd = len;
    } else if (idxStart < (UINT)firstSel) {
        uSearchEnd = firstSel;
    } else if (idxStart > (UINT)lastSel) {
        uSearchEnd = len;
    }
    if (!clr_ar) {    
        *idxEnd = uSearchEnd;
        return;
    } 
    
    while (*idxEnd < uSearchEnd && clr_ar[*idxEnd] == clr_ar[idxStart]) {
        (*idxEnd) ++;
    }
    return;
}

void CEditWindow::DrawColoredTextSegment(HDC dc, CTextLine* lineobj, UINT uStart, UINT count, COLORREF clr, bool selected, RECT* rcSeg)
{
    assert (lineobj);
    assert (rcSeg);
    // top, left and bottom are initialize, we need to calc the right.
    
    UINT uFormat = DT_BOTTOM | DT_SINGLELINE |DT_NOPREFIX | DT_EXPANDTABS | DT_TABSTOP;
    DRAWTEXTPARAMS dtp = {sizeof(dtp), 0, 0, 0, 0};
    dtp.iTabLength = m_uTabSize;

    rcSeg->right = (lineobj->IndexToDisplayCol(uStart + count, m_uTabSize)) * m_uCharWidth;
    if (selected) {
        HBRUSH brSel = (HBRUSH)(COLOR_HIGHLIGHT+1);
        SetTextColor (dc, GetSysColor(COLOR_HIGHLIGHTTEXT));
        FillRect (dc, rcSeg, brSel);
    } else {
        SetTextColor (dc, clr);
    }
    DrawTextEx (dc, lineobj->GetArrayPtr()+uStart, count, rcSeg, uFormat, &dtp);
}

UINT CEditWindow::GetShownLines()
{
    assert (m_uLineHeight != 0);

    RECT rc;    
    GetClientRect(&rc);
    return  (rc.bottom - rc.top) / m_uLineHeight;
}

UINT CEditWindow::GetShownChars()
{
    assert (m_uCharWidth != 0);

    RECT rc;    
    GetClientRect(&rc);
    UINT uWndTextWidth = rc.right - rc.left;
    if (m_bShowLineNumber) {
        uWndTextWidth -= GetLineNumberAreaWidth(rc.bottom - rc.top,
                                                m_uLineHeight,
                                                m_uFirstShownLine,
                                                m_uCharWidth);
        uWndTextWidth -= LEADING_SPACE;
    }
    return uWndTextWidth/m_uCharWidth;
}

void CEditWindow::SetScrollBars()
{
    assert (m_uLineHeight * m_uCharWidth != 0);
    UINT uShownLines = GetShownLines();
    if (uShownLines >= m_doc.m_nValidLines) {
        SetScrollRange(SB_VERT, 0, 0, FALSE);
        ShowScrollBar(SB_VERT, FALSE);
    } else {
        SetScrollRange(SB_VERT, 0, m_doc.m_nValidLines - uShownLines, FALSE);
        ShowScrollBar(SB_VERT, TRUE);
    }

    UINT uShownChars = GetShownChars();
    UINT uMaxLength = m_doc.GetMaxLineLength() + TRAILING_CHARS;

    if (uShownChars >=  uMaxLength) {
        SetScrollRange(SB_HORZ, 0, 0, FALSE);
        ShowScrollBar(SB_HORZ, FALSE);
    } else {
        SetScrollRange(SB_HORZ, 0, uMaxLength - uShownChars, FALSE);
        ShowScrollBar(SB_HORZ, TRUE);
    }
}

void CEditWindow::SetCaretPosAndShow()
{
    if (GetFocus() != m_hWnd) {
        return;
    }

    int x, y;
    bool visible = GetCaretPos(&x, &y);
    if (!visible) {
        HIDE_CARET();
        /* Before calling this routine, caret is hiding*/
        return;
    } 
    SetCaretPos(x, y); 
    SHOW_CARET(); 
    SetCompositionForm();
}

/* Returns the position of Caret, can be outside of the client rect.
 *  if out of client rect, returns false, otherwise ture.
 */
bool CEditWindow::GetCaretPos(int* x, int* y)
{
    UINT uLineNbrWidth = 0;
    RECT rc;
    GetClientRect(&rc);
    bool visible;

    *y = ((int)m_uCurLine - (int)m_uFirstShownLine) * m_uLineHeight; 
    *x = ((int)m_uCurCol - (int)m_uFirstShownChar) * m_uCharWidth; 
    if (m_bShowLineNumber) {
        uLineNbrWidth = GetLineNumberAreaWidth(rc.bottom-rc.top, 
                                            m_uLineHeight,
                                            m_uFirstShownLine,
                                            m_uCharWidth);
    }
    if (*x < 0 || *y < 0 || *x > rc.right || *y > rc.bottom) {
        visible = false;
    } else {
        visible = true;
    }
    *x += uLineNbrWidth + LEADING_SPACE;

    return visible;
}

void CEditWindow::XY2LineCol(int x, int y, unsigned int* line, unsigned int* col)
{
    assert (m_uLineHeight);
    assert (m_uCharWidth);
    assert (line && col);

    RECT rc;
    GetClientRect(&rc);

    UINT uLineNbrWidth = 0;
    if (m_bShowLineNumber) {
        uLineNbrWidth = GetLineNumberAreaWidth(rc.bottom-rc.top, 
                                            m_uLineHeight,
                                            m_uFirstShownLine,
                                            m_uCharWidth);
    }

    if (y < 0) {
        y = 0;
    }
    if (y > rc.bottom) {
        y = rc.bottom;
    }
    *line = (int)(y / m_uLineHeight) + m_uFirstShownLine;
    if (x < LEADING_SPACE + (int)uLineNbrWidth) {
        *col = m_uFirstShownChar;
    } else {
        *col = (x - LEADING_SPACE- uLineNbrWidth) / m_uCharWidth + 
                    ((x - LEADING_SPACE- uLineNbrWidth) % m_uCharWidth > m_uCharWidth/2) +
                    m_uFirstShownChar;
    }

    if (*line >= m_doc.m_nValidLines) {
        *line = m_doc.m_nValidLines-1;
        *col = m_doc.GetLineObject(*line)->GetTotalCols(m_uTabSize);
    } else {
        UINT uLineLength = m_doc.GetLineObject(*line)->GetTotalCols(m_uTabSize);
        if (*col > uLineLength) {
            *col = uLineLength;
        }
    }
    CTextLine* textline = m_doc.GetLineObject(*line);
    UINT idx = textline->DisplayColToIndex(*col, m_uTabSize);
    *col = textline->IndexToDisplayCol(idx, m_uTabSize);
    return;
}

void CEditWindow::AdjustViewPort (int xcursor, int ycursor)
{
    assert (IsWindow());
    assert (m_uLineHeight > 0);
    assert (m_uCharWidth > 0);

    RECT rc;
    GetClientRect(&rc);

    if (ycursor < 0) {
        (m_uFirstShownLine != 0  ? m_uFirstShownLine -- : m_uFirstShownLine);
    } else if (ycursor > rc.bottom) {
        if (m_uFirstShownLine < m_doc.m_nValidLines - (rc.bottom - rc.top) / m_uLineHeight) {
            m_uFirstShownLine ++;
        }
    }

    UINT uLineNbrWidth = 0;
    if (m_bShowLineNumber) {
        uLineNbrWidth = GetLineNumberAreaWidth(rc.bottom-rc.top, 
                                            m_uLineHeight,
                                            m_uFirstShownLine,
                                            m_uCharWidth);
    }
    if (xcursor < (int)(uLineNbrWidth + LEADING_SPACE)) {
        (m_uFirstShownChar != 0 ? m_uFirstShownChar -- : m_uFirstShownChar);
    } else if (xcursor > rc.right) {
        CTextLine* line = m_doc.GetLineObject(m_uCurLine);
        if (line->GetTotalCols(m_uTabSize) > (rc.right-rc.left)/m_uCharWidth && 
            (m_uFirstShownChar < line->GetTotalCols(m_uTabSize) - (rc.right-rc.left)/m_uCharWidth)) {
            m_uFirstShownChar ++;
        }
    }
}

void CEditWindow::SetCompositionFont()
{
    HIMC hIMC;
    assert (IsWindow());
    assert (m_hFont);
    
    hIMC = ImmGetContext(m_hWnd);
    if (hIMC)
    {
        LOGFONT lf;
        GetObject(m_hFont, sizeof(lf), &lf);

        ImmSetCompositionFont(hIMC, &lf);
        ImmReleaseContext(m_hWnd, hIMC);
    }
}

#define IME_FORM_SIZE 50

void CEditWindow::SetCompositionForm()
{
    HIMC hIMC;
    RECT rc;

    assert (IsWindow());
    assert (m_hFont);
    assert (m_uLineHeight);
    
    GetClientRect(&rc);
    hIMC = ImmGetContext(m_hWnd);
    if (hIMC) {
        int x, y;
        COMPOSITIONFORM CompForm;
        
        GetCaretPos(&x, &y);
        if (x < 0) {
            x = 0;
        } else if (x > rc.right - IME_FORM_SIZE) {
            x = rc.right - IME_FORM_SIZE;
        }
        if (y < 0) {
            y = 0;
        } else if ((UINT)y > rc.bottom - m_uLineHeight) {
            y = rc.bottom - m_uLineHeight;
        }

        CompForm.dwStyle = CFS_POINT;
        CompForm.ptCurrentPos.x = x + m_uCharWidth/2;
        CompForm.ptCurrentPos.y = y + m_uLineHeight/2;
        ImmSetCompositionWindow(hIMC, &CompForm);
        ImmReleaseContext(m_hWnd, hIMC);
    }
}

#define SAVE_CUR_LINE_COL()   \
    UINT prevFirstLine = m_uFirstShownLine; \
    UINT prevFirstChar = m_uFirstShownChar; \
    UINT prevLine = m_uCurLine; 

#define NEED_TO_UPDATE_ALL() (m_sel.Selected() || (prevFirstLine != m_uFirstShownLine ||prevFirstChar != m_uFirstShownChar) )

#define PARTLY_UPDATE() do {\
    RECT rc;    \
    GetClientRect(&rc);     \
    UINT line0 = (m_uCurLine > prevLine ? prevLine : m_uCurLine); \
    UINT line1 = (m_uCurLine + prevLine - line0); \
    if (line0 == line1) {   \
        break;  \
    } \
    rc.top = (line0 - m_uFirstShownLine) * m_uLineHeight;   \
    rc.bottom = (line1 - m_uFirstShownLine + 1) * m_uLineHeight;    \
    InvalidateRect(&rc, FALSE); \
} while (0)

void CEditWindow::OnVkHome()
{
    SAVE_CUR_LINE_COL()

    m_uCurCol = 0; 
    m_uTargetedCol = 0;
    m_uFirstShownChar = 0;
    SetScrollPos (SB_HORZ, m_uFirstShownChar);
    if (NEED_TO_UPDATE_ALL()) {
        m_sel.SetSelection(0, 0, 0, 0);
        Invalidate(FALSE);
        return;
    }
    PARTLY_UPDATE();
}

void CEditWindow::OnVkEnd()
{
    SAVE_CUR_LINE_COL();
    UINT uCharsPerLine = GetShownChars();

    m_uCurCol = (m_doc.GetLineObject(m_uCurLine))->GetTotalCols(m_uTabSize); 
    m_uTargetedCol = m_uCurCol;
    if (m_uCurCol > m_uFirstShownChar + uCharsPerLine) {
        m_uFirstShownChar = ((m_doc.GetLineObject(m_uCurLine))->GetTotalCols(m_uTabSize) + HORZ_MOVE_IN_ADVANCE) - uCharsPerLine;
        SetScrollPos (SB_HORZ, m_uFirstShownChar);
    }
    if (NEED_TO_UPDATE_ALL()) {
        m_sel.SetSelection(0, 0, 0, 0);
        Invalidate(FALSE);
        return;
    }
    PARTLY_UPDATE();
}

void CEditWindow::OnVkPrior()
{
    UINT uLinesPerScreen;
    uLinesPerScreen = GetShownLines();

    m_uCurLine = (m_uCurLine >= uLinesPerScreen ? m_uCurLine - uLinesPerScreen : 0);
    m_uFirstShownLine = (m_uFirstShownLine >= uLinesPerScreen ? m_uFirstShownLine - uLinesPerScreen : 0);

    m_uCurCol = MIN(m_uTargetedCol, (m_doc.GetLineObject(m_uCurLine))->GetTotalCols(m_uTabSize));
    CTextLine* line = m_doc.GetLineObject(m_uCurLine);
    UINT idx = line->DisplayColToIndex(m_uCurCol, m_uTabSize);
    m_uCurCol = line->IndexToDisplayCol(idx, m_uTabSize);

    SetCurPosVisible();
    m_sel.SetSelection(0, 0, 0, 0);

    Invalidate(FALSE);
}

void CEditWindow::OnVkNext()
{
    UINT uLinesPerScreen;
    uLinesPerScreen = GetShownLines();

    m_uCurLine = MIN (m_uCurLine + uLinesPerScreen, m_doc.m_nValidLines-1);
    if (m_doc.m_nValidLines < uLinesPerScreen) {
        m_uFirstShownLine = 0;
    } else {
        m_uFirstShownLine = MIN (m_uFirstShownLine + uLinesPerScreen, m_doc.m_nValidLines - uLinesPerScreen);
    }

    m_uCurCol = MIN(m_uTargetedCol, (m_doc.GetLineObject(m_uCurLine))->GetTotalCols(m_uTabSize));
    CTextLine* line = m_doc.GetLineObject(m_uCurLine);
    UINT idx = line->DisplayColToIndex(m_uCurCol, m_uTabSize);
    m_uCurCol = line->IndexToDisplayCol(idx, m_uTabSize);

    SetCurPosVisible();
    m_sel.SetSelection(0, 0, 0, 0);
    Invalidate(FALSE);
}

void CEditWindow::OnVkLeft()
{
    SAVE_CUR_LINE_COL();

    CTextLine* line = m_doc.GetLineObject(m_uCurLine);
    UINT uCharsPerLine = GetShownChars();
    if (m_uCurCol > 0) {
        m_uCurCol --;
        UINT idx = line->DisplayColToIndex(m_uCurCol, m_uTabSize);
        m_uCurCol = line->IndexToDisplayCol(idx, m_uTabSize);
        m_uTargetedCol = m_uCurCol;
        SetCurPosVisible();
    } else if (m_uCurLine != 0) {
        OnVkUp();
        OnVkEnd();
    } else {
        /* reach the beginning of the file.*/
        return;
    }
    if (NEED_TO_UPDATE_ALL() ) {
        m_sel.SetSelection(0, 0, 0, 0);
        Invalidate(FALSE);
        return;
    }
    PARTLY_UPDATE();
}

void CEditWindow::OnVkRight()
{
    SAVE_CUR_LINE_COL();

    UINT uCharsPerLine = GetShownChars();
    CTextLine* line = m_doc.GetLineObject(m_uCurLine);
    if (m_uCurCol < line->GetTotalCols(m_uTabSize)) {
        m_uCurCol ++;
        UINT idx = line->DisplayColToIndex(m_uCurCol, m_uTabSize, true);
        m_uCurCol = line->IndexToDisplayCol(idx, m_uTabSize);
        m_uTargetedCol = m_uCurCol;
        SetCurPosVisible();
    } else if (m_uCurLine != m_doc.m_nValidLines-1) {
        OnVkDown();
        OnVkHome();
    } 
    if (NEED_TO_UPDATE_ALL() ) {
        m_sel.SetSelection(0, 0, 0, 0);
        Invalidate(FALSE);
        return;
    }
    PARTLY_UPDATE();
}

void CEditWindow::OnVkUp()
{
    SAVE_CUR_LINE_COL();

    UINT uLinesPerScreen;
    uLinesPerScreen = GetShownLines();

    m_uCurLine = (m_uCurLine >= 1 ? m_uCurLine - 1 : 0);
    CTextLine* line = m_doc.GetLineObject(m_uCurLine);
    m_uCurCol = MIN(m_uTargetedCol, line->GetTotalCols(m_uTabSize));
    UINT idx = line->DisplayColToIndex(m_uCurCol, m_uTabSize);
    m_uCurCol = line->IndexToDisplayCol(idx, m_uTabSize);
    
    SetCurPosVisible();
    if (NEED_TO_UPDATE_ALL() ) {
        m_sel.SetSelection(0, 0, 0, 0);
        Invalidate(FALSE);
        return;
    }
    PARTLY_UPDATE();
}

void CEditWindow::OnVkDown()
{
    SAVE_CUR_LINE_COL();

    UINT uLinesPerScreen;
    uLinesPerScreen = GetShownLines();

    m_uCurLine = MIN (m_uCurLine + 1, m_doc.m_nValidLines-1);
    m_uCurCol = MIN(m_uTargetedCol, (m_doc.GetLineObject(m_uCurLine))->GetTotalCols(m_uTabSize));
    CTextLine* line = m_doc.GetLineObject(m_uCurLine);
    UINT idx = line->DisplayColToIndex(m_uCurCol, m_uTabSize);
    m_uCurCol = line->IndexToDisplayCol(idx, m_uTabSize);

    m_sel.SetSelection(0, 0, 0, 0);
    SetCurPosVisible();
    if (NEED_TO_UPDATE_ALL() ) {
        m_sel.SetSelection(0, 0, 0, 0);
        Invalidate(FALSE);
        return;
    }
    PARTLY_UPDATE();
}

void CEditWindow::OnVkDel()
{
    if (m_sel.Selected()) {
        DeleteSelected();
    } else {
        DeleteNextChar();
    }
    SetCurPosVisible();
    Invalidate(FALSE);
    return;
}

void CEditWindow::OnVkF3()
{
    bool up = (GetKeyState(VK_SHIFT) < 0 ? true : false);

    SetFocus();
    FindNextInDoc(m_strFind, NULL, up);
    SetCurPosVisible();
    Invalidate(FALSE);
}

void CEditWindow::SetCurPosVisible()
{
    UINT uCharsPerLine = GetShownChars();
    UINT uLinesPerScreen = GetShownLines();

    if (m_uCurCol < m_uFirstShownChar ) {
        m_uFirstShownChar = m_uCurCol;
    } else if ( m_uCurCol + 1 > m_uFirstShownChar + uCharsPerLine) {
        m_uFirstShownChar = m_uCurCol - uCharsPerLine;
    }
    if (m_uCurLine < m_uFirstShownLine) {
        m_uFirstShownLine = m_uCurLine;
    } else if (m_uCurLine + 1 > m_uFirstShownLine +uLinesPerScreen) {
        m_uFirstShownLine = m_uCurLine - uLinesPerScreen + 1;
    }
    SetScrollPos (SB_HORZ, m_uFirstShownChar);
    SetScrollPos (SB_VERT, m_uFirstShownLine);
    return; 
}

void CEditWindow::ChangeCurLineByMouse(LPARAM lParam)
{
    XY2LineCol(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), &m_uCurLine, &m_uCurCol);
    if (m_hWnd != GetFocus()) {
        SetFocus();
    } else {
        HIDE_CARET();
        SetCaretPosAndShow();
    }

    Invalidate(FALSE);
    m_uTargetedCol = m_uCurCol;
    return;
}

void CEditWindow::GetCurLineRect(RECT* rc)
{
    GetClientRect(rc);
    rc->top = ((int)m_uCurLine - (int)m_uFirstShownLine) * m_uLineHeight;
    rc->bottom = rc->top + m_uLineHeight;
}

void CEditWindow::InsertCharToCurPos(char c)
{
    UINT uIdx;
    CTextLine* line;
    char str[2]; 
    str[1] = 0;
    str[0]  = c;
    
    m_stack.StartTransaction();
    DeleteSelected();
    line = m_doc.GetLineObject(m_uCurLine);
    uIdx = line->DisplayColToIndex(m_uCurCol, m_uTabSize);
    m_doc.InsertTextAt(m_uCurLine, uIdx, str);
    m_stack.EndTransaction();

    m_syntaxLighter.FindAllBlocks();

    SetScrollBars();
    OnVkRight();
}

int CEditWindow::InsertImeString()
{
    HIMC  hIMC;
    DWORD dwLen;
    LPSTR lpResultStr;

    hIMC = ImmGetContext(m_hWnd);
    if (!hIMC) {
        return -1;
    }

    dwLen = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0L);
    if (dwLen) {
        lpResultStr = (LPSTR)malloc(dwLen+1);
        memset (lpResultStr, 0, dwLen+1);
        if (lpResultStr) {
            UINT idx;
            CTextLine* line = m_doc.GetLineObject(m_uCurLine);
            ImmGetCompositionString(hIMC,GCS_RESULTSTR, lpResultStr,dwLen+1);
            idx = line->DisplayColToIndex(m_uCurCol, m_uTabSize);
            /* IME only returns a signal line, so m_uCurLine shall not change.*/
            m_doc.InsertTextAt(m_uCurLine, idx, lpResultStr, NULL, &idx);
            m_syntaxLighter.FindAllBlocks();
            free(lpResultStr);
            m_uCurCol = line->IndexToDisplayCol(idx, m_uTabSize);
        }
    }
    ImmReleaseContext(m_hWnd,hIMC);
    return 0;
}

void CEditWindow::OnBackSpace()
{
    if (m_sel.Selected()) {
        DeleteSelected();
        return;
    }
    DeletePrevChar();
    return;
}

void CEditWindow::DeleteNextChar()
{
    CTextLine* line;
    UINT idx;
    
    line = m_doc.GetLineObject(m_uCurLine);
    idx = line->DisplayColToIndex(m_uCurCol, m_uTabSize);

    if (m_uCurLine == m_doc.m_nValidLines -1 && 
          idx == strlen(m_doc.GetLineText(m_uCurLine))) {
        /* Already at the end of the doc. */
        return;
    }
    
    if (idx == strlen(m_doc.GetLineText(m_uCurLine))) {
        m_doc.RemoveText(m_uCurLine, idx, m_uCurLine, EOL);
        m_syntaxLighter.FindAllBlocks();
        return;
    }
    
    UINT next_idx = idx+1;
    UINT col = line->IndexToDisplayCol(next_idx, m_uTabSize);
    next_idx = line->DisplayColToIndex(col, m_uTabSize, true);
    m_doc.RemoveText(m_uCurLine, idx, m_uCurLine, next_idx-1);
    m_syntaxLighter.FindAllBlocks();
    return;
}

void CEditWindow::DeletePrevChar()
{
    CTextLine* line;
    UINT idx;

    line = m_doc.GetLineObject(m_uCurLine);
    idx = line->DisplayColToIndex(m_uCurCol, m_uTabSize);

    if (m_uCurLine == 0 && idx == 0) {
        /* Already at the beginning of the doc.*/
        return;
    }

    if (idx) {
        UINT idx_old = idx;
        m_uCurCol = line->IndexToDisplayCol(--idx, m_uTabSize);
        idx = line->DisplayColToIndex(m_uCurCol, m_uTabSize);
        m_uCurCol = line->IndexToDisplayCol(idx, m_uTabSize);
        m_doc.RemoveText(m_uCurLine, idx, m_uCurLine, idx_old-1);
        m_syntaxLighter.FindAllBlocks();
        return;
    }

    assert (idx == 0);
    assert (m_uCurLine >= 1);
    idx = strlen(m_doc.GetLineText(m_uCurLine-1));
    m_doc.RemoveText(m_uCurLine-1, idx, m_uCurLine-1, EOL);
    m_syntaxLighter.FindAllBlocks();
    m_uCurLine--;
    line = m_doc.GetLineObject(m_uCurLine);
    m_uCurCol = line->IndexToDisplayCol(idx, m_uTabSize);
}

void CEditWindow::DeleteSelected()
{
    UINT small_l, small_c, big_l, big_c;
    UINT sidx, bidx;

    if (!m_sel.GetAscendantInclusiveSel(&small_l, &small_c, &big_l, &big_c)) {
        return;
    } 

    SEL_COL_2_IDX (small_l, small_c, big_l, big_c, sidx, bidx);
    m_doc.RemoveText(small_l, sidx, big_l, bidx);
    m_syntaxLighter.FindAllBlocks();
    m_uCurLine = small_l;
    m_uCurCol = small_c;
    m_sel.SetSelection(0, 0, 0, 0);
}

void CEditWindow::SelectAll()
{
    m_sel.SetSelection(0,0, LAST_LINE, EOL);
    Invalidate(FALSE);
}

void CEditWindow::FindNextInDoc(const char* str, CTextSelection* pFoundSel, bool up/*=false*/)
{
    if (!str || str[0] == '\0') {
        return;
    }

    assert (m_selFind.Selected());
    UINT sl, sc, bl, bc, si, bi;
    m_selFind.GetAscendantSel(&sl, &sc, &bl, &bc);
    if (up) {
        if (CompareTextPosition(bl, bc,m_uCurLine, m_uCurCol) >= 0 &&
            CompareTextPosition(sl, sc, m_uCurLine, m_uCurCol) <= 0) {
            bl = m_uCurLine;
            bc = m_uCurCol;
        }
    } else {
        if (CompareTextPosition(bl, bc,m_uCurLine, m_uCurCol) >= 0 &&
            CompareTextPosition(sl, sc, m_uCurLine, m_uCurCol) <= 0) {
            sl = m_uCurLine;
            sc = m_uCurCol;
        }
    }
    SEL_COL_2_IDX(sl,sc,bl,bc,si,bi);
    CTextSelection range, foundsel;
    range.SetSelection(sl, si, bl, bi);
    bool found;
    if (up) {
        found = m_doc.FindInSelectionBackward(&range, str, &foundsel);
    } else {
        found = m_doc.FindInSelectionForward(&range, str, &foundsel);
    }

    if (!found) {
        m_selFind.GetAscendantSel(&sl, &sc, &bl, &bc);
        if (up) {
            m_uCurLine = (bl == LAST_LINE ? m_doc.m_nValidLines-1 : bl);
            m_uCurCol = (bc == EOL ? (m_doc.GetLineObject(m_uCurLine))->GetTotalCols(m_uTabSize) : bc);
        } else {
            m_uCurLine = sl;
            m_uCurCol = sc;
        }
        m_sel.SetSelection(0, 0, 0, 0);
    } else {
        foundsel.GetAscendantSel(&sl, &si, &bl, &bi);
        CTextLine* line = m_doc.GetLineObject(sl);
        assert (line);
        sc = line->IndexToDisplayCol(si, m_uTabSize);
        assert (bl == sl);
        bc = line->IndexToDisplayCol(bi, m_uTabSize);
        m_sel.SetSelection(sl, sc, bl, bc);
        if (up) {
            m_uCurLine = sl; 
            m_uCurCol = sc;
        } else {
            m_uCurLine = bl; 
            m_uCurCol = bc;
        }
    }
    if (pFoundSel) {
        *pFoundSel = foundsel;
    }
}

char* CEditWindow::SetFindString(const char* str)
{
    assert (str);
    assert (str[0] != '\0');
    
    if (m_strFind) {
        free (m_strFind);
        m_strFind = NULL;
    }
    m_strFind = (char*) malloc(strlen(str) + 1);
    if (m_strFind) {
        strcpy(m_strFind, str);
    }
    return m_strFind;
}

void CEditWindow::AdjustFindSelEndingCol(CTextSelection* pFoundSel, const char* str0, const char* str1)
{
    assert (pFoundSel);
    assert (pFoundSel->Selected());
    assert (str0);
    assert (str0[0] != '\0');
    assert (str1);
    assert (m_selFind.Selected());

    /* if replacing happens in the last line of m_selFind,
     *  the ending col need to be adjusted.
     */
    UINT r_sl, r_sc, r_bl, r_bc;
    pFoundSel->GetAscendantSel(&r_sl, &r_sc, &r_bl, &r_bc);
    assert (r_sl == r_bl);

    UINT sl, bl, sc, bc;
    m_selFind.GetAscendantSel(&sl, &sc, &bl, &bc);
    if (r_sl == bl) {
        UINT index = m_doc.GetLineObject(bl)->DisplayColToIndex(bc, m_uTabSize);
        index = index + strlen(str1) - strlen(str0);
        bc = m_doc.GetLineObject(bl)->IndexToDisplayCol(index, m_uTabSize);
        m_selFind.SetSelection(sl, sc, bl, bc);
    }
}

int CEditWindow::CompareSelection(CTextSelection* sel, const char* str)
{
    /* To be enhanced.
     * This implementation only supports a selection in a line.
     * Multiple lines selection comparison is not supported.
     */
    assert (sel);
    assert (str);

    if (!sel->Selected()) {
        return -1;
    }
    UINT sl, si, bl, bi;
    sel->GetAscendantInclusiveSel(&sl, &si, &bl, &bi);
    assert (sl == bl);
    CTextLine* line = m_doc.GetLineObject(sl);
    return (strncmp((m_doc.GetLineText(sl) + si),  str, strlen(str)));
}

void CEditWindow::CopySelectionTo(unsigned int line, unsigned int col)
{
    UINT sl, sc, bl, bc;
    UINT si, bi;
    if (!m_sel.GetAscendantInclusiveSel(&sl,&sc,&bl,&bc)) {
        return;
    }
    SEL_COL_2_IDX(sl,sc,bl,bc,si,bi);
    size_t size = m_doc.GetTextLength(sl,si,bl,bi) + 1;
    char* buf = (char*)malloc (size);
    if (!buf) {
        return;
    }
    memset (buf, 0, size);
    m_doc.GetText(sl,si,bl,bi, buf, size);

    UINT idx = (m_doc.GetLineObject(line))->DisplayColToIndex(col, m_uTabSize);
    m_doc.InsertTextAt(line, idx, buf);
    free(buf);
    m_doc.SetDirty();
    m_syntaxLighter.FindAllBlocks();
    return;
}

void CEditWindow::MoveSelectionTo(unsigned int* line, unsigned int* col)
{
    assert (line);
    assert (col);

    UINT sl, sc, bl, bc;
    UINT si, bi;
    if (!m_sel.GetAscendantInclusiveSel(&sl,&sc,&bl,&bc)) {
        return;
    }
    SEL_COL_2_IDX(sl,sc,bl,bc,si,bi);
    size_t size = m_doc.GetTextLength(sl,si,bl,bi) + 1;
    char* buf = (char*)malloc (size);
    if (!buf) {
        return;
    }
    memset (buf, 0, size);
    m_doc.GetText(sl,si,bl,bi, buf, size);

    UINT idx = (m_doc.GetLineObject(*line))->DisplayColToIndex(*col, m_uTabSize);
    GetBlockMoveInsertPos(line, &idx, &m_sel);

    m_stack.StartTransaction();
    m_doc.RemoveText(sl, si, bl, bi);
    m_doc.InsertTextAt(*line, idx, buf);
    m_stack.EndTransaction();
    
    free(buf);
    m_doc.SetDirty();
    m_syntaxLighter.FindAllBlocks();
    return;
}

void CEditWindow::ReDo()
{
    unsigned int rowCursor;
    unsigned int colCursor;

    int ret = m_stack.ReDoCurOperations(&rowCursor, &colCursor);
    if (ret < 0) {
        return;
    }
    m_uCurLine = (rowCursor == LAST_LINE ? m_doc.m_nValidLines -1 : rowCursor);
    CTextLine* line = m_doc.GetLineObject(m_uCurLine);
    m_uCurCol = line->IndexToDisplayCol(colCursor, m_uTabSize);
    m_syntaxLighter.FindAllBlocks();
    SetScrollBars();
    SetCurPosVisible();
    m_sel.SetSelection(0, 0, 0, 0);

    Invalidate(FALSE);
}

void CEditWindow::UnDo()
{
    unsigned int rowCursor;
    unsigned int colCursor;

    int ret = m_stack.UnDoCurOperations(&rowCursor, &colCursor);
    if (ret < 0) {
        return;
    }
    m_uCurLine = (rowCursor == LAST_LINE ? m_doc.m_nValidLines -1 : rowCursor);
    CTextLine* line = m_doc.GetLineObject(m_uCurLine);
    m_uCurCol = line->IndexToDisplayCol(colCursor, m_uTabSize);
    m_syntaxLighter.FindAllBlocks();
    SetScrollBars();
    SetCurPosVisible();
    m_sel.SetSelection(0, 0, 0, 0);

    Invalidate(FALSE);
}


void CEditWindow::InsertTextToCurPos(char* lpstr) 
{
    m_stack.StartTransaction();
    DeleteSelected();
    CTextLine* line = m_doc.GetLineObject(m_uCurLine);
    UINT idx = line->DisplayColToIndex(m_uCurCol, m_uTabSize);
    m_doc.InsertTextAt(m_uCurLine, idx, lpstr, &m_uCurLine, &idx);
    m_stack.EndTransaction();
    m_doc.SetDirty();
    m_syntaxLighter.FindAllBlocks();
    line = m_doc.GetLineObject(m_uCurLine);
    m_uCurCol = line->IndexToDisplayCol(idx, m_uTabSize);
    SetScrollBars();
    SetCurPosVisible();
    Invalidate(FALSE);
}

static HFONT CreateEditFont()
{
    LOGFONT lf;
    HFONT hFont;
    lf.lfCharSet = ANSI_CHARSET;
    lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
    lf.lfEscapement = 0;
    strncpy (lf.lfFaceName, FONT_NAME, LF_FACESIZE);
    lf.lfHeight = FONT_SIZE;
    lf.lfItalic = 0;
    lf.lfOrientation = 0;
    lf.lfOutPrecision = OUT_DEFAULT_PRECIS;
    lf.lfPitchAndFamily = DEFAULT_PITCH;
    lf.lfQuality = DEFAULT_QUALITY;
    lf.lfStrikeOut = 0;
    lf.lfUnderline = 0;
    lf.lfWeight = FW_NORMAL;
    lf.lfWidth = 0;
    hFont = CreateFontIndirect(&lf);
    return hFont;
}

static UINT GetLineNumberAreaWidth (UINT uWndHeight, UINT uLineHeight, UINT uStartLine, UINT uCharWidth)
{
    UINT uEndLine;

    uEndLine = (uWndHeight%uLineHeight==0) ?  (uWndHeight/uLineHeight) : (uWndHeight/uLineHeight+1);
    uEndLine += uStartLine; 
    UINT uDigits = 1;
    while (uEndLine >= 10) {
        uDigits ++;
        uEndLine /= 10;
    }
    uDigits = (uDigits < 2 ? 2 : uDigits);
    uDigits = (uDigits > MAX_LINENBR_DIGITS ? MAX_LINENBR_DIGITS : uDigits);
    return uDigits * uCharWidth;
}

static char* CreateSpaceStringForTab(size_t spaces)
{
    char* tabStr = (char*) malloc (spaces + 1);
    if (!tabStr) {
        return NULL;
    }
    memset(tabStr, ' ', spaces);
    tabStr[spaces] = '\0';
    return tabStr;
}